class Parser
token
  NUM ID DEF
  READ WRITE
  PLUS MINUS MUL DIV
  SEMI NL LPAR RPAR
  WHILE IF ELSE END
  EQ NE GT GE LT LE
  COMMA DOLLAR
  FUNCTION
rule
  qq: bb { @lvars={}; @ln=0 } t {
  result=Code.new
  result.push 'sal '+(SYSREGS.size+@gvars.size).to_s, 'jmp LMAIN'
  result.push val[0]
  result.push 'LMAIN'
  result.push 'con '+(SYSREGS.size+@gvars.size).to_s
  result.push 'gto RS'
  result.push 'sal '+@lvars.size.to_s
  val[2].cnum=@lvars.size; result.push val[2]
  @funcs.each {|k,v| abort "function #{k} is not defined" unless v[1]}
  result.push 'hal'
}

  bb: bb aa {result.push val[1]}
   | {result=Code.new}

  aa: FUNCTION fid LPAR dparams RPAR optsep t END sep
       { 
         pnum=val[3].size
         if @funcs[val[1]]
           abort "function #{val[1]} is already defined" if @funcs[val[1]][1]
           abort "parameter number error (#{val[1]})" unless pnum==@funcs[val[1]][0]
           @funcs[val[1]][1]=true
         else
           @funcs[val[1]]=[pnum,true]
         end

         result=Code.new
         result.push 'L'+val[1],'sal '+(@lvars.size-1-pnum).to_s
         val[6].cnum=@lvars.size-1-pnum; result.push val[6]
         (@lvars.size-1).times {|i| result.push 'pop'}
         SYSREGS.reverse_each {|i| result.push 'gto '+i }
         result.push 'jpo'
       }

  fid: ID { @fid=val[0]; @lvars={@fid=>0}; @ln=0 }

  dparams: dparam {
      @lvars[@fid]=-(3+SYSREGS.size+val[0].size)
      val[0].each_index {|i| @lvars[val[0][i]]=i-val[0].size}
    }
   | { result=[]; @lvars[@fid]=-(3+SYSREGS.size) }

  dparam: dparam COMMA ID {
    if @lvars.include?(val[2])
      abort "variable #{val[2]} already exists"
    end
    result.push val[2]
    @lvars[val[2]]=0
  }
  | ID {
    if @lvars.include?(val[0])
      abort "variable #{val[0]} already exists"
    end
    result=[val[0]]
    @lvars[val[0]]=0
  }

  t: t s { result.push val[1] }
   | { result=Code.new }

  s: WRITE e sep { result=val[1].push 'pri' }
   | ID DEF e sep { unless k=@lvars[val[0]]
                      k=@lvars[val[0]]=@ln
                      @ln+=1
                    end
                    result=val[2].push 'con '+k.to_s
                    result.push 'god RS'
                    result.push 'add'
                    result.push 'gto'
                  }
   | DOLLAR ID DEF e sep { 
                    unless k=@gvars.index(val[1])
                      @gvars.push(val[1])
                      k=@gvars.size-1
                    end
                    result=val[3].push 'gto '+(SYSREGS.size+k).to_s
                  }
   | READ ID sep { unless k=@lvars[val[1]]
                     k=@lvars[val[1]]=@ln
                     @ln+=1
                   end
                   result=Code.new
                   result.push 'red', 'con '+k.to_s
                   result.push 'god RS'
                   result.push 'add'
                   result.push 'gto'
                 }
   | READ DOLLAR ID sep { 
                    unless k=@gvars.index(val[2])
                      @gvars.push(val[2])
                      k=@gvars.size-1
                    end
                    result=Code.new.push 'red', 'gto '+(SYSREGS.size+k).to_s
                  }
   | WHILE cond optsep t END sep {
                               l0=@lab.next; l1=@lab.next; l2=@lab.next
                               result=Code.new
                               result.push l0
                               result.push val[1]
                               result.push 'jpo '+l1
                               result.push 'jmp '+l2
                               result.push l1
                               result.push val[3]
                               result.push 'jmp '+l0
                               result.push l2
                             }
   | IF cond optsep t END sep {
                            l1=@lab.next; l2=@lab.next
                            result=val[1]
                            result.push 'jpo '+l1
                            result.push 'jmp '+l2
                            result.push l1
                            result.push val[3]
                            result.push l2
                          }

   | IF cond optsep t ELSE optsep t END sep {
                            l1=@lab.next; l2=@lab.next
                            result=val[1]
                            result.push 'jpo '+l1
                            result.push val[6]
                            result.push 'jmp '+l2
                            result.push l1
                            result.push val[3]
                            result.push l2
                          }
   | func sep { result.push 'pop' }

  optsep: sep
   |

  sep: SEMI
   | NL
   | SEMI NL

  cond: e EQ e { l1=@lab.next; l2=@lab.next
                 result.push 'gto RC0'
                 result.push val[2]
                 result.push 'gto RC1'
                 result.push 'god RC0'
                 result.push 'god RC1'
                 result.push 'sub'
                 result.push 'jpo '+l1
                 result.push 'god RC1'
                 result.push 'god RC0'
                 result.push 'sub'
                 result.push 'jpo '+l1
                 result.push 'con 1'
                 result.push 'jmp '+l2
                 result.push l1
                 result.push 'con 0'
                 result.push l2
               }
   | e NE e { l1=@lab.next; l2=@lab.next
              result.push 'gto RC0'
              result.push val[2]
              result.push 'gto RC1'
              result.push 'god RC0'
              result.push 'god RC1'
              result.push 'sub'
              result.push 'jpo '+l1
              result.push 'god RC1'
              result.push 'god RC0'
              result.push 'sub'
              result.push 'jpo '+l1
              result.push 'con 0'
              result.push 'jmp '+l2
              result.push l1
              result.push 'con 1'
              result.push l2
            }
   | e GT e { l0=@lab.next; l1=@lab.next
              val[2].cnum=1; result.push val[2]
              result.push 'sub'
              result.push 'jpo '+l0
              result.push 'con 0'
              result.push 'jmp '+l1
              result.push l0
              result.push 'con 1'
              result.push l1
            }
   | e GE e { l0=@lab.next; l1=@lab.next
              result=val[2]
              val[0].cnum=1; result.push val[0]
              result.push 'sub'
              result.push 'jpo '+l0
              result.push 'con 1'
              result.push 'jmp '+l1
              result.push l0
              result.push 'con 0'
              result.push l1
            }
   | e LT e { l0=@lab.next; l1=@lab.next
              result=val[2]
              val[0].cnum=1; result.push val[0]
              result.push 'sub'
              result.push 'jpo '+l0
              result.push 'con 0'
              result.push 'jmp '+l1
              result.push l0
              result.push 'con 1'
              result.push l1
            }
   | e LE e { l0=@lab.next; l1=@lab.next
              val[2].cnum=1; result.push val[2]
              result.push 'sub'
              result.push 'jpo '+l0
              result.push 'con 1'
              result.push 'jmp '+l1
              result.push l0
              result.push 'con 0'
              result.push l1
            }

  e: e PLUS f { val[2].cnum=1; result.push val[2],'add' }
   | e MINUS f { val[2].cnum=1; result.push val[2],'sub' }
   | f

  f: f MUL g { val[2].cnum=1; result.push val[2],'mul' }
   | f DIV g { val[2].cnum=1; result.push val[2],'div' }
   | g

  g: NUM { result=Code.new.push 'con '+val[0] }
   | MINUS g {
      if val[1].size==1 and val[1][0]=~/\Acon\s+(-?\d+)\z/
        val[1][0]="con #{-($~[1].to_i)}"
        result=val[1]
      else
        val[1].cnum=1; result=Code.new.push 'con 0',val[1],'sub'
      end
    }
   | PLUS g { result=val[1] }
   | ID { unless k=@lvars[val[0]]
            k=@lvars[val[0]]=@ln
            @ln+=1
          end
          result=Code.new.push 'con '+k.to_s
          result.push 'god RS'
          result.push 'add'
          result.push 'god'
        }
   | DOLLAR ID {
       unless k=@gvars.index(val[1])
         @gvars.push(val[1])
         k=@gvars.size-1
       end
       result=Code.new.push 'god '+(SYSREGS.size+k).to_s
     }
   | LPAR e RPAR { result=val[1] }
   | func

  func: ID LPAR params RPAR {
         k=val[2].size
         if @funcs[val[0]]
           abort "parameter number error (#{val[0]})" unless k==@funcs[val[0]][0]
         else
           @funcs[val[0]]=[k,false]
         end

         l0=@lab.next
         result=Code.new.push 'con 0', 'con 1'
         result.push 'con '+l0
         SYSREGS.each {|j| result.push 'god '+j }
         result.push val[2]
         result.push 'god RS'
         c=Code.new.push 'CON'; c.cnum=3+SYSREGS.size+k; result.push c
         result.push 'add'
         result.push 'gto RS'
         result.push 'jmp L'+val[0]
         result.push l0
       }

  params: param
   | { result=Code.new }

  param: param COMMA e { val[2].cnum=val[0].size; result.push val[2] }
   | e { result=Code.new.push val[0]; result.cnum=3+SYSREGS.size }
end
---- header
SYSREGS=['RS', 'RC0', 'RC1']

class Code < Array
  attr_accessor :cnum

  def addCON(k=0)
    out=Code.new
    k=k+@cnum if @cnum

    each {|a|
      if a.instance_of?(Code)
        out.concat a.addCON(k)
      elsif a=='CON'
        out.push "con #{k}"
      else
        out.push a
      end
    }

    return out
  end

  def as
    out=Code.new

    each {|h|
      if h=~/\Asal\s+(\d+)\z/
        $~[1].to_i.times {|i| out.push 'con 0'}
      elsif h=~/\Agod\s+(.+)\z/
        out.push 'con '+$~[1]
        out.push 'god'
      elsif h=~/\Agto\s+(.+)\z/
        out.push 'con '+$~[1]
        out.push 'gto'
      elsif h=~/\Ajpo\s+(.+)\z/
        out.push 'con '+$~[1]
        out.push 'jpo'
      elsif h=~/\Ajmp\s+(.+)\z/
        out.push 'con 1'
        out.push 'con '+$~[1]
        out.push 'jpo'
      else
        out.push h
      end
    }

    h={}

    for i in 0...SYSREGS.size
      h[SYSREGS[i]]=i.to_s
    end

    j=0
    for i in 0...out.size
      if out[i]=~/\AL\S+\z/
        h[$~[0]]=j.to_s
        out[i]=nil
      else
        j+=1
      end
    end
    out.compact!

    for i in 0...out.size
      if out[i]=~/[A-Z]\S+/ and h[$~[0]]
        out[i].sub!($~[0],h[$~[0]])
      end
    end

    return out
  end
end

class Label
  def initialize
    @i=-1
  end

  def next
    @i+=1
    "L#{@i}"
  end
end
---- inner
def parse(tokens)
  @a=tokens
  @lab=Label.new
  @gvars=[]
  @funcs={}

  do_parse
end

def next_token
  @a.shift
end
---- footer
require 'strscan'

def scan(file)
  begin
    f=open(file)
  rescue
    abort $!
  end

  t=[]
  s=StringScanner.new('')
  while str=f.gets
    s.string=str
    a=[]
    until s.eos?
      case
      when s.scan(/\#/)
        s.terminate
      when s.scan(/\s+/)
      when s.scan(/function/)
        a.push [:FUNCTION,0]
      when s.scan(/write/)
        a.push [:WRITE,0]
      when s.scan(/read/)
        a.push [:READ,0]
      when s.scan(/while/)
        a.push [:WHILE,0]
      when s.scan(/if/)
        a.push [:IF,0]
      when s.scan(/else/)
        a.push [:ELSE,0]
      when s.scan(/end/)
        a.push [:END,0]
      when s.scan(/\:\=/)
        a.push [:DEF,0]
      when s.scan(/\>\</)
        a.push [:NE,0]
      when s.scan(/\>\=/)
        a.push [:GE,0]
      when s.scan(/\<\=/)
        a.push [:LE,0]
      when s.scan(/\=/)
        a.push [:EQ,0]
      when s.scan(/\>/)
        a.push [:GT,0]
      when s.scan(/\</)
        a.push [:LT,0]
      when s.scan(/\+/)
        a.push [:PLUS,0]
      when s.scan(/\-/)
        a.push [:MINUS,0]
      when s.scan(/\*/)
        a.push [:MUL,0]
      when s.scan(/\//)
        a.push [:DIV,0]
      when s.scan(/\;/)
        a.push [:SEMI,0]
      when s.scan(/\,/)
        a.push [:COMMA,0]
      when s.scan(/\$/)
        a.push [:DOLLAR,0]
      when s.scan(/\(/)
        a.push [:LPAR,0]
      when s.scan(/\)/)
        a.push [:RPAR,0]
      when s.scan(/\d+/)
        a.push [:NUM,s[0]]
      when s.scan(/[a-z]+/)
        a.push [:ID,s[0]]
      else
        abort 'unknown token at "'+s.string.chomp+'"'
      end
    end

    unless a.empty?
      t.concat a
      t.push [:NL,0]
    end
  end

  t.push [false,0]
  return t
end

if ARGV.size==1
  fin=ARGV[0]
  fout='epro.code'
elsif ARGV.size==3 and ARGV[0]=='-o'
  fin=ARGV[2]
  fout=ARGV[1]
elsif ARGV.size==3 and ARGV[1]=='-o'
  fin=ARGV[0]
  fout=ARGV[2]
else
  abort "usage: ruby #{$0} [ -o output ] source"
end

tok=scan(fin)

begin
  code=Parser.new.parse tok
rescue ParseError
  abort $!
end

code=code.addCON.as

begin
  f=open(fout,'w')
rescue
  abort $!
end
code.each {|i| f.puts i}
f.close
